import java.util.LinkedList;

public class Parser {
	
	public static Data parse(String input) {
		SchavaReader chars = new SchavaReader(input);
		Data token = Parser.readToken(chars);
		chars.eatWhitespace();
		String remainingChars = chars.toString();
		return Primitive.cons(token, remainingChars);
	}

	public static Data readToken(SchavaReader chars) {
		chars.eatWhitespace();
		if (!chars.hasNext()) {
			return Evaluator.COMMENT;
		}
		char first = chars.peekChar();
		if (isComment(first)) {
			chars.readChar();
			chars.eatLine();
			return Evaluator.COMMENT;
		} else if (isNumber(first)) {
			return readNumber(chars);
		} else if (isSymbol(first)) {
			return readSymbol(chars);
		} else if (isDoubleQuote(first)) {
			return readString(chars);
		} else if (isQuote(first)) {
			return readQuote(chars);
		} else if (isOpenParen(first)) {
			return readList(chars);
		} else if (isCloseParen(first)) {
			throw new SchavaException("Unexpected close parenthesis");
		}
		throw new SchavaException("Unknown input: " + chars.toString());
	}


	public static Data readNumber(SchavaReader chars) {
		StringBuilder str = new StringBuilder();
		boolean decimalFlag = false;
		while (chars.hasNext()) {
			if (isDecimal(chars.peekChar())) {
				if (decimalFlag && str.length() > 0) {
					return new SchavaNumber(str.toString());
				} else {
					str.append(chars.readChar());
					decimalFlag = true;
				}
			} else if (isNumber(chars.peekChar())) {
				str.append(chars.readChar());
			} else {
				break;
			}
		}
		return new SchavaNumber(str.toString());
	}

	public static Data readSymbol(SchavaReader chars) {
		StringBuilder str = new StringBuilder();
		while (chars.hasNext() && isSymbol(chars.peekChar())) {
			str.append(chars.readChar());
		}
		return new SchavaSymbol(str.toString());
	}

	public static Data readString(SchavaReader chars) {
		StringBuilder str = new StringBuilder();
		chars.readChar();
//		str.append(chars.readChar());
		while (chars.hasNext() && !isDoubleQuote(chars.peekChar())) {
			str.append(chars.readChar());
			if (chars.isEmpty()) {
				throw new UnfinishedLineException();
			}
		}
		if (!chars.hasNext()) {
			throw new UnfinishedLineException("String");
		}
		chars.readChar();
//		str.append(chars.readChar());
		return new SchavaString(str.toString());
	}

	public static Data readQuote(SchavaReader chars) {
		chars.readChar();
		Data token = readToken(chars);
		return new Pair(new SchavaSymbol("quote"), 
				new Pair(token, Evaluator.THE_EMPTY_LIST));
	}

	public static Data readList(SchavaReader chars) {
		chars.readChar();
		LinkedList<Data> items = new LinkedList<Data>();
		while (chars.hasNext() && !isCloseParen(chars.peekChar())) {
			Data item = readToken(chars); 
			items.add(item);
			chars.eatWhitespace();
			if (chars.hasNext() && isDecimal(chars.peekChar())) {
				chars.readChar();
				Data returnList = Primitive.append(Primitive.list(items),
						readToken(chars));
				chars.eatWhitespace();
				if (chars.hasNext()) {
					if (isCloseParen(chars.peekChar())) {
						chars.readChar();
						return returnList;
					} else {
						throw new SchavaException("Parser readList: Missing close parenthesis: " 
								+  chars.toString());
					}
				} else {
					throw new UnfinishedLineException(returnList.toString());
				}
			}
		}
		if (chars.hasNext() && isCloseParen(chars.peekChar())) {
			chars.readChar();
		} else {
			throw new UnfinishedLineException(chars.toString());
		}
		return Primitive.list(items);
	}


	public static boolean isComment(char _char) {
		return _char == ';';
	}

	public static boolean isDecimal(char _char) {
		return _char == '.';
	}

	public static boolean isNumber(char _char) {
		return '0' <= _char && _char <= '9';
	}

	public static boolean isDoubleQuote(char _char) {
		return _char == '"';
	}

	public static boolean isQuote(char _char) {
		return _char == '\'';
	}

	public static boolean isSymbol(char _char) {
		return ((33 <= _char && _char <= 126)
				&& !member(_char, "\"&'().:,[]`")
				|| member(_char, "<>"));
	}

	public static boolean member(char _char, String _string) {
		if (_string == null || _string.length() == 0) {
			return false;
		} else if (_string.charAt(0) == _char) {
			return true;
		} else {
			return member(_char, _string.substring(1));
		}
	}

	public static boolean isOpenParen(char _char) {
		return _char == '(';
	}

	public static boolean isCloseParen(char _char) {
		return _char == ')';
	}



}
